package fag.core.game.test.playground;

import java.awt.Dimension;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.util.List;

import javax.swing.JFrame;
import javax.swing.SwingUtilities;

import fag.core.collision.Collision;
import fag.core.game.AbstractGame;
import fag.core.game.Transformable.Referential;
import fag.core.game.test.playground.solid.AbstractSolid;

import fag.core.geometry.Vector2D;
import fag.core.geometry.Winding;

/**
 * Classe du bac à sable. Destiner à faire la démonstration des performances du framework.
 * @author lumz
 */
public class Playground extends AbstractGame implements MouseListener, MouseMotionListener {
	
	public static final Dimension FRAME_SIZE = new Dimension(800, 600);
	public static final Dimension CAMERA_SIZE = new Dimension(800, 600);
	
	public static final JFrame frame = new JFrame("Playground FAG");

	public static final PlaygroundCamera camera = new PlaygroundCamera();
	public static final PlaygroundScene scene = new PlaygroundScene(1500, 1500);
	public static final Playground playground = new Playground();
	
	/**
	 * Lanceur du bac à sable.
	 * @param args Les arguments du programme.
	 */
	public static void main(String[] args) {
		
		playground.start();
		
		SwingUtilities.invokeLater(new Runnable() {

			@Override
			public void run() {
				
				frame.setSize(FRAME_SIZE);
				frame.setVisible(true);
				frame.setResizable(false);
				frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
				frame.add(camera);
				
				camera.setSize(CAMERA_SIZE);
				camera.addMouseListener(playground);
				camera.addMouseMotionListener(playground);
				camera.setTranslation(scene.getWidth() / 2 - camera.getWidth() / 2, scene.getHeight() / 2 - camera.getHeight() / 2);
			}
		});
	}
	
	/**
	 * Constructeur par défaut.
	 */
	public Playground() {
		
		addScene(scene);
		scene.addCamera(camera);
	}
	
	@Override
	public void mouseDragged(MouseEvent event) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void mouseMoved(MouseEvent event) {
		
		if(scene.isInitialized()) {
		
			// Calcul de la position de la caméra
			Vector2D target = new Vector2D((CAMERA_SIZE.getWidth() - event.getX()) * scene.getWidth() / CAMERA_SIZE.getWidth() - CAMERA_SIZE.getWidth() / 2,
					(CAMERA_SIZE.getHeight() - event.getY()) * scene.getHeight() / CAMERA_SIZE.getHeight() - CAMERA_SIZE.getHeight() / 2);
			
			// Maintenir dans les limites de la scène
			if(target.x < 0)
				target.x = 0;
			else if(target.x > scene.getWidth() - CAMERA_SIZE.getWidth())
				target.x = scene.getWidth() - CAMERA_SIZE.getWidth();
			
			if(target.y < 0)
				target.y = 0;
			else if(target.y > scene.getHeight() - CAMERA_SIZE.getHeight())
				target.y = scene.getHeight() - CAMERA_SIZE.getHeight();
			
			camera.setTranslation(target);
		}
	}

	@Override
	public void mouseClicked(MouseEvent event) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void mouseEntered(MouseEvent event) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void mouseExited(MouseEvent event) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void mousePressed(MouseEvent event) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void mouseReleased(MouseEvent event) {
		// TODO Auto-generated method stub
		
	}
	
	@Override
	public void step(double dt) {

		collisionResponse(dt / 1000);
	}

	/**
	 * Gestion des collisions.
	 */
	private void collisionResponse(double dt) {
		
		List<AbstractSolid> solids = scene.getSolids();
		
		// Gestion des collisions
		for(int i = 0; i < solids.size() - 1; ++i) {
			for(int j = i + 1; j < solids.size(); ++j) {
				
				AbstractSolid a = solids.get(i);
				AbstractSolid b = solids.get(j);
				
				Collision collision = a.getHitbox().getCollision(b.getHitbox());
				
				if(collision.isColliding()) {
					
					Vector2D normal = collision.getNormal();
					double penetration = collision.getPenetration();
					List<Vector2D> contacts = collision.getContactManifold();
					
					// Pour chaque point de contact
					for(Vector2D p : contacts) {
						
						Vector2D rayAP = p.sub(a.getTranslation());
						Vector2D rayBP = p.sub(b.getTranslation());
						
						Vector2D perpRayAP = rayAP.normal(Winding.COUNTERCLOCKWISE);
						Vector2D perpRayBP = rayBP.normal(Winding.COUNTERCLOCKWISE);
						
						Vector2D velocityAP = perpRayAP.mul(a.getAngularVelocity()).add(a.getLinearVelocity());
						Vector2D velocityBP = perpRayBP.mul(b.getAngularVelocity()).add(b.getLinearVelocity());
						
						// Calcul de l'impulsion
						double impulse = (velocityAP.sub(velocityBP).mul(-(1.0 + (a.getElasticity() + b.getElasticity()) / 2)).dot(normal)) / 
								(normal.dot(normal.mul((1.0 / a.getMass() + 1.0 / b.getMass()))) + (Math.pow(perpRayAP.dot(normal), 2) / a.getMomentOfInertia()) + (Math.pow(perpRayBP.dot(normal), 2) / b.getMomentOfInertia()));
						
						impulse = impulse / contacts.size();
						
						// Réponse
						a.setLinearVelocity(a.getLinearVelocity().add(normal.mul(impulse / a.getMass())));
						a.setAngularVelocity(a.getAngularVelocity() + perpRayAP.dot(normal.mul(impulse)) / a.getMomentOfInertia());
						b.setLinearVelocity(b.getLinearVelocity().add(normal.mul(-impulse / b.getMass())));
						b.setAngularVelocity(b.getAngularVelocity() + perpRayBP.dot(normal.mul(-impulse)) / b.getMomentOfInertia());
					}
					
					// Séparation
					a.translate(normal.mul(penetration / 2).add(normal).mul(-1), Referential.ABSOLUTE);
					b.translate(normal.mul(penetration / 2).add(normal), Referential.ABSOLUTE);
				}
			}
		}
	}
}
